package  com.fyyg.common.page;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;

import com.fyyg.common.util.LogicUtil;


/**
 * 分页拦截器
 * @author hang.ouyang
 *
 * @date 2016年3月16日 下午4:44:44
 */
@Intercepts({ @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class PageInterceptor implements Interceptor {
	private static final Log log = LogFactory.getLog(PageInterceptor.class);

	private static final String DIALECT_MYSQL = "mysql";
	private static final String DB_TYPE = "dbType";// 数据库类型，不同的数据库有不同的分页方法

	private String dbType;

	/**
	 * 拦截后要执行的方法
	 */
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
		StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
		BoundSql boundSql = delegate.getBoundSql();
		Object obj = boundSql.getParameterObject();
		PageParam<?> page = getPageParam(obj);
		if (page != null) {
			MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
			Connection connection = (Connection) invocation.getArgs()[0];
			String sql = boundSql.getSql();
			if(page.isQueryTotal() == true) {
				this.setTotalRow(page, mappedStatement, connection);
			}
			if(page.getDataTotal() <= 0)
				return invocation.proceed();
			String pageSql = this.getPageSql(page, sql);
			ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
		}
		return invocation.proceed();
	}

	private PageParam<?> getPageParam(Object obj) {
		if (obj instanceof PageParam<?>) {
			return (PageParam<?>) obj;
		}
		if (obj instanceof HashMap) {
			HashMap<?, ?> map = (HashMap<?, ?>) obj;
			for (Entry<?, ?> e : map.entrySet()) {
				if (e.getValue() instanceof PageParam<?>) {
					return (PageParam<?>) e.getValue();
				}
			}
		}
		return null;
	}

	/**
	 * 注册插件
	 */
	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	/**
	 * 获取配置属性
	 */
	@Override
	public void setProperties(Properties properties) {
		this.dbType = properties.getProperty(DB_TYPE);
	}

	/**
	 * 获取分页查询sql
	 * @param pageParam
	 * @param sql
     * @return
     */
	private String getPageSql(PageParam<?> pageParam, String sql) {
		if (DIALECT_MYSQL.equalsIgnoreCase(dbType)) {
			return getMysqlPageSql(pageParam, sql);
		} else {
			log.error("mybatis page intecepter db type not support");
			return null;
		}
	}

	/**
	 * 给当前的参数对象page设置总记录数
	 * @param page  参数对象
	 * @param mappedStatement  Mapper映射语句
	 * @param connection 当前的数据库连接
	 * @author hang.ouyang
	 * @date 2016年3月16日 下午4:52:08
	 */
	private void setTotalRow(PageParam<?> page,MappedStatement mappedStatement, Connection connection) {
		BoundSql boundSql = mappedStatement.getBoundSql(page);
		String sql = boundSql.getSql();
		String totalRowSql = this.getTotalRowSql(sql);
		List<ParameterMapping> totalRowParams = this.getTotalRowParams(boundSql.getParameterMappings(), totalRowSql);
		BoundSql totalBoundSql = new BoundSql(mappedStatement.getConfiguration(), totalRowSql,totalRowParams, page);
		ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, totalBoundSql);
		try(PreparedStatement preparedStatement = connection.prepareStatement(totalRowSql)){
			parameterHandler.setParameters(preparedStatement);
			try(ResultSet rs = preparedStatement.executeQuery()){
				if (rs.next()) {
					int rowTotal = rs.getInt(1);// 返回总数
					page.setDataTotal(rowTotal);// 设置总记录数
				}
			}
		} catch (SQLException e) {
			log.error("set total row error", e);
		}
	}

	private String getTotalRowSql(String sql) {
		 return "select count(0) from (" + sql + ") total";
	}

	/**
	 * 获取所有查询参数
	 * @param parameterMappings
	 * @param countSql
	 * @return
	 * @author hang.ouyang
	 * @date 2016年3月16日 下午4:54:40
	 */
	private List<ParameterMapping> getTotalRowParams(
			List<ParameterMapping> parameterMappings, String countSql) {
		int size = parameterMappings.size();
		int count = this.getCount(countSql, "?");

		List<ParameterMapping> paramMappings = new ArrayList<ParameterMapping>();
		if (size >= count && count > 0) {
			paramMappings.addAll(parameterMappings.subList(size - count, size));
		}
		return paramMappings;
	}
	
	/**
	 * 参数个数
	 * @param str
	 * @param key
	 * @return
	 * @author hang.ouyang
	 * @date 2016年3月16日 下午5:13:41
	 */
	private int getCount(String str, String key) {
		if (LogicUtil.isNotNullAndEmpty(str)
				&& LogicUtil.isNotNullAndEmpty(key)) {
			int c = 0;
			int b = -1;
			int i = str.indexOf(key, b);
			while (i > -1) {
				c++;
				b = i + key.length();
				i = str.indexOf(key, b);
			}
			return c;
		}
		return 0;
	}



	/**
	 * Mysql数据库的分页语句
	 * @param pageParam
	 * @param sql
	 * @return
	 * @author hang.ouyang
	 * @date 2016年3月16日 下午5:09:48
	 */
	private String getMysqlPageSql(PageParam<?> pageParam, String sql) {
		int pageNo = pageParam.getPageNo() <= 0 ? 1 : pageParam.getPageNo();
		int startSet = (pageNo - 1) * pageParam.getPageSize();
		return sql + " limit " + startSet + "," +  pageParam.getPageSize();
	}
	

	/**
	 * 反射工具类
	 * @author hang.ouyang
	 *
	 * @date 2016年3月16日 下午5:48:59
	 */
	private static class ReflectUtil {
		
		/**
		 * 利用反射获取指定对象的指定属性
		 * @param obj  目标对象
		 * @param fieldName 目标属性
		 * @return 目标属性的值
		 * @author hang.ouyang
		 * @date 2016年3月16日 下午5:48:40
		 */
		public static Object getFieldValue(Object obj, String fieldName) {
			Field field = getField(obj, fieldName);
			if (field == null) {
				return null;
			}

			Object result = null;
			try {
				field.setAccessible(true);
				result = field.get(obj);
			} catch (IllegalArgumentException e) {
				log.error("illegal argument", e);
			} catch (IllegalAccessException e) {
				log.error("illegal access", e);
			}

			return result;
		}

		/**
		 * 利用反射获取指定对象里面的指定属性
		 * @param obj 目标对象
		 * @param fieldName  目标属性
		 * @return 目标字段
		 * @author hang.ouyang
		 * @date 2016年3月16日 下午5:10:49
		 */
		private static Field getField(Object obj, String fieldName) {
			Field field = null;
			for (Class<?> clazz = obj.getClass(); clazz != Object.class; 
					clazz = clazz.getSuperclass()) {
				try {
					field = clazz.getDeclaredField(fieldName);
					break;
				} catch (NoSuchFieldException e) {
					// 这里不用做处理，子类没有该字段可能对应的父类有，都没有就返回null。
				}
			}
			return field;
		}

		/**
		 * 利用反射设置指定对象的指定属性为指定的值
		 * @param obj 目标对象
		 * @param fieldName 目标属性
		 * @param fieldValue 目标值
		 * @author hang.ouyang
		 * @date 2016年3月16日 下午5:10:07
		 */
		public static void setFieldValue(Object obj, String fieldName,
				String fieldValue) {
			Field field = getField(obj, fieldName);
			if (field == null) {
				return;
			}
			try {
				field.setAccessible(true);
				field.set(obj, fieldValue);
			} catch (IllegalArgumentException e) {
				log.error("illegal argument", e);
			} catch (IllegalAccessException e) {
				log.error("illegal access", e);
			}
		}
	}
}
